package main

// Starting with version 1.18, Go has added support for
// generics, also known as type parameters.

import "fmt"

// As an example of a generic function, SlicesIndex takes a slice
// of any comparable type and an element of that type and returns
// the index of the first occurrence of v in s, or -1 if not present.
// The comparable constraint means that we can compare values of this
// type with the == and != operators.
func SlicesIndex[S ~[]E, E comparable](s S, v E) int {
	for i := range s {
		if s[i] == v {
			return i
		}
	}
	return -1
}

// As an example of a generic type, List is a singly-linked list with
// values of any type
type List[T any] struct {
	head, tail *element[T]
}

type element[T any] struct {
	next *element[T]
	val  T
}

func (lst *List[T]) Push(v T) {
	if lst.head == nil {
		lst.head = &element[T]{val: v}
		lst.tail = lst.head
	} else {
		lst.tail.next = &element[T]{val: v}
		lst.tail = lst.tail.next
	}
}

func (lst *List[T]) AllElements() []T {
	var elems []T
	for e := lst.head; e != nil; e = e.next {
		elems = append(elems, e.val)
	}
	return elems
}

func main() {
	var s = []string{"foo", "bar", "zoo"}

	// When invoking generic functions, we can oftern rely on type
	// inference. Note that we don't have to specify the types for S and E
	// when calling SlicesIndex, the compiler infers them automatically.
	fmt.Println("index of zoo:", SlicesIndex(s, "zoo"))

	// though we could also specify them explicitly.
	_ = SlicesIndex[[]string, string](s, "zoo")

	lst := List[int]{}
	lst.Push(10)
	lst.Push(13)
	lst.Push(23)
	fmt.Println("list:", lst.AllElements())
}
